﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using SharpPcap;
using SharpPcap.Packets;
using Hids.Enums;
using Hids.Classes;
using Hids.Classes.AttackSignatures.PacketTests;

namespace Hids.Classes.AttackSignatures
{
    /// <summary>
    /// This class represents a specific type of attack to look for within one packet.  There are several different types of PacketTests
    /// that can be used to test the packet for the attack.
    /// </summary>
    public class AttackSignature
    {
        #region Private Properties

        /// <summary>
        /// Holds the destination port to look at
        /// </summary>
        private int _destinationPort;

        /// <summary>
        /// Holds whether or not the destination port has been set
        /// </summary>
        private bool _destinationPortSet = false;

        /// <summary>
        /// Holds the time the attack was last detected for each IP address, used to prevent multiple alerts from the same attack
        /// </summary>
        private Dictionary<string, DateTime> _timeLastDetectedByIpAddress = new Dictionary<string, DateTime>();

        #endregion

        #region Public Properties

        /// <summary>
        /// The message to display when an attack is found
        /// </summary>
        public string NotificationMessage;

        /// <summary>
        /// The destination port to filter packets
        /// </summary>
        public int DestinationPort
        {
            get
            {
                return _destinationPort;
            }
            set
            {
                _destinationPortSet = true;
                _destinationPort = value;
            }
        }

        /// <summary>
        /// Whether or not the destination port has been set
        /// </summary>
        public bool DestinationPortSet
        {
            get
            {
                return _destinationPortSet;
            }
        }

        /// <summary>
        /// Holds the amount of time to wait before one attack is considered two separate attacks
        /// </summary>
        public TimeSpan TimeToWaitBetweenAlerts = new TimeSpan(0, 0, 5, 0, 0);//5 minutes

        /// <summary>
        /// The direction of the packet
        /// </summary>
        public TrafficDirection Direction = TrafficDirection.Both;

        /// <summary>
        /// Holds the various tests to perform on the packet
        /// </summary>
        public List<PacketTest> PacketTests = new List<PacketTest>();

        #endregion

        #region Constructors

        /// <summary>
        /// Default constructor
        /// </summary>
        public AttackSignature() { }

        /// <summary>
        /// Creates an attack signature from an Xml Node's Attributes and Child Nodes
        /// </summary>
        /// <param name="signatureNode">The Xml Node to create the signature from.  The Packet tests should be given as a child node of the signature
        /// Xml Node.  Valid Attributes and their values are:
        /// NotificationMessage:  string
        /// DestinationPort:  integer
        /// TimeToWaitBetweenAlerts:  integer
        /// </param>
        public AttackSignature(XmlNode signatureNode)
        {
            #region Load the signature's attributes

            foreach (XmlAttribute attribute in signatureNode.Attributes)
            {
                switch (attribute.Name)
                {
                    case "NotificationMessage":
                        NotificationMessage = attribute.Value;
                        break;
                    case "DestinationPort":
                        DestinationPort = int.Parse(attribute.Value);
                        break;
                    case "TimeToWaitBetweenAlerts":
                        TimeToWaitBetweenAlerts = TimeSpan.Parse(attribute.Value);
                        break;
                    default:
                        throw new ApplicationException("Unknown attribute:  " + attribute.Name);
                }
            }

            if (NotificationMessage == "")
                throw new ApplicationException("No Notification Message Set");

            #endregion

            #region Load the signature's packet tests

            foreach (XmlNode packetTest in signatureNode.ChildNodes)
            {
                switch (packetTest.Name)
                {
                    case "Content":
                        PacketTests.Add(new Content(packetTest.Attributes));
                        break;
                    case "ByteTest":
                        PacketTests.Add(new ByteTest(packetTest.Attributes));
                        break;
                    case "Pcre":
                        PacketTests.Add(new Pcre(packetTest.Attributes));
                        break;
                    default:
                        throw new ApplicationException("Unknown Packet Test:  " + packetTest.Name);
                }
            }

            if (PacketTests.Count == 0)
                throw new ApplicationException("No Packet Tests Set");

            #endregion
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// This method tests the passed in packet for the specific attack using its different packet tests
        /// </summary>
        /// <param name="packet">The packet to check for the specific attack</param>
        /// <returns>Whether or not this attack has occurred</returns>
        public bool DetectTcpAttack(Packet packet)
        {
            TCPPacket tcpPacket = packet as TCPPacket;
            if (tcpPacket == null)
                return false;//if it's not a TCP packet, quit

            if (!_timeLastDetectedByIpAddress.ContainsKey(tcpPacket.SourceAddress.ToString())) //initalize the dictionary
                _timeLastDetectedByIpAddress.Add(tcpPacket.SourceAddress.ToString(), new DateTime(1,1,1));

            if (tcpPacket.PcapHeader.Date.Subtract(_timeLastDetectedByIpAddress[tcpPacket.SourceAddress.ToString()]) <= TimeToWaitBetweenAlerts)
                return false;//if the attack was already found within the last [TimeToWaitBetweenAlerts] from this IP address, then we don't need to continually write alerts

            if ((!DestinationPortSet || tcpPacket.DestinationPort == DestinationPort) 
                && (Direction == TrafficDirection.Both || (Direction == TrafficDirection.Incoming && tcpPacket.DestinationAddress.ToString() == Program.IpAddress)
                    || (Direction == TrafficDirection.Outgoing && tcpPacket.SourceAddress.ToString() == Program.IpAddress)))
            {//ensure the traffic is going to the right port and in the right direction (only look at packets for our computer)
                int offset = 0;
                foreach (PacketTest packetTest in PacketTests)
                {
                    if (packetTest is Content)
                    {
                        Content searchForBytes = packetTest as Content;
                        offset = searchForBytes.ContainsBytes(tcpPacket.Data, offset);
                        if (offset == -1)//if the content was not found, than the packet isn't an attack of this kind
                            return false;
                    }
                    else if(packetTest is ByteTest)
                    {
                        ByteTest testBytes = packetTest as ByteTest;
                        if (!testBytes.TestBytes(tcpPacket.Data, offset))
                            return false;//if the bytes failed the test, than the packet isn't an attack of this kind
                    }
                    else if (packetTest is Pcre)
                    {
                        Pcre regularExpressionTest = packetTest as Pcre;
                        if (!regularExpressionTest.RegularExpressionTest(tcpPacket.Data, offset))
                            return false;//if the regular expression was not found, than the packet isn't an attack of this kind
                    }
                }
                string notificationMessage = NotificationMessage + " At " + tcpPacket.PcapHeader.Date.ToString() + " By " + tcpPacket.SourceAddress.ToString();
                Logs.WriteMessageToLog(notificationMessage);//write to the logs
                Notifications.SendEmail(notificationMessage);//send a notification
                Console.WriteLine(notificationMessage);//write a message to the screen
                _timeLastDetectedByIpAddress[tcpPacket.SourceAddress.ToString()] = tcpPacket.PcapHeader.Date;//save the time so we don't alert multiple times for the same attack
                return true;
            }
            return false;
        }

        #endregion
    }
}
